/*
-----------------------------------------------------------------------------
This source file is part of Labor3D
(Labor 3D Graphics Engine)

Copyright (c) 2009-2020 Rock Mao
* creator : Rock Mao
* created : 2009-5-2   22:29
* filename: LaborCamera.cpp
-----------------------------------------------------------------------------
*/

#include "Renderer\LaborCamera.h"
#include "LaborCore.h"
#include "Renderer\LaborRenderSystem.h"

NAMESPACE_LABOR3D_BEGIN

//---------------------------------------------------------------------
CLCamera::CLCamera()
{
	// Init camera location & direction

	m_bFixed = true;
	// Locate at (0,0,0)
	m_vPos.x = m_vPos.y = m_vPos.z = 0;
	m_vLookAt.x = m_vLookAt.y = m_vLookAt.z = 0;
	m_vDirection.x = m_vDirection.y = m_vDirection.z = 0.f;
	m_vRight.x = m_vRight.y = m_vRight.z = 0.f;
	m_vUp.x = m_vUp.y = m_vUp.z = 0.f;

	m_vDirection.z = -1.f;
	m_vRight.x = 1.f;
	m_vUp.y = 1.f;


	// Point down -Z axis
	//m_quatRot.

	m_matrixView.identity();

	// Reasonable defaults to camera params
	m_fFOVy = PI*0.25f;
	m_fNearDist = 0.1f;
	m_fFarDist = 500.0f;
	m_fAspect = 1.33333333333333f;
	mProjType = ProjectionType_PERSPECTIVE;

	m_fYawAngle = 0.f;
	m_fPitchAngle = 0.f;
	m_fRollAngle = 0.f;

	_frustumChanged();
	_viewChanged();

	// no reflection
	m_bReflectEnabled = false;
}

//---------------------------------------------------------------------
CLCamera::CLCamera(CLCamera *camera)
{
	m_bFixed = camera->m_bFixed;

	m_vPos			= camera->m_vPos;
	m_vLookAt		= camera->m_vLookAt;
	m_vDirection	= camera->m_vDirection;
	m_vRight		= camera->m_vRight;
	m_vUp			= camera->m_vUp;

	m_matrixView.identity();

	// Reasonable defaults to camera params
	m_fFOVy = camera->m_fFOVy;
	m_fNearDist = camera->m_fNearDist;
	m_fFarDist = camera->m_fFarDist;
	m_fAspect = camera->m_fAspect;
	mProjType = camera->mProjType;

	m_fYawAngle		= camera->m_fYawAngle;
	m_fPitchAngle	= camera->m_fPitchAngle;
	m_fRollAngle	= camera->m_fRollAngle;

	_frustumChanged();
	_viewChanged();

	// no reflection
	m_bReflectEnabled = camera->m_bReflectEnabled;
}

//---------------------------------------------------------------------
CLCamera::~CLCamera()
{

}

void CLCamera::init( const CLPoint& ptPos, const CLPoint& ptStart, const CLPoint& ptEnd )
{
	CLPoint ptLookAt = ( ptStart + ptEnd ) / 2;


	setPosition( ptPos );
	lookAt( ptLookAt );	

	/// get distance
	float fDis = ptLookAt | ptPos;

	/// get direction
	ptLookAt -= ptPos;

	/// new plane
	CLPlane planeNew;

	planeNew.n = ptLookAt.normalize();
	planeNew.d = fDis;

	CLPoint ptNewStart = ptStart;
	CLPoint ptNewEnd = ptEnd;

	/// project to the plane
	ptNewStart.projectToPlane( planeNew );
	ptNewEnd.projectToPlane( planeNew );

	setAspect( 1.0f );

	float fAngle = ( ptStart | ( ptStart + ptEnd ) ) / ( 1.414f * ( ptPos | ( ptStart + ptEnd ) ) );

	setFOVy( fAngle );
}

void CLCamera::_updateFrustum(void)
{
	CLFrustum::_updateFrustum();
}

void CLCamera::_updateView(void)
{
	CLFrustum::_updateView();

	if ( m_bViewChanged )
	{
		// ----------------------
		// Update the view matrix
		// ----------------------

		// View matrix is:
		//
		//  [ Lx  Uy  Dz  Tx  ]
		//  [ Lx  Uy  Dz  Ty  ]
		//  [ Lx  Uy  Dz  Tz  ]
		//  [ 0   0   0   1   ]
		//
		// Where T = -(Transposed(Rot) * Pos)


		m_matrixView.identity();

		CLPoint vAt = m_vPos+m_vDirection;
		if( m_bFixed )
		{
			CLPoint up( 0.f, 1.f, 0.f );
			m_matrixView.lookAtLH( &m_vPos , &vAt , &up );
		}
		else
		{
			m_matrixView.lookAtLH( &m_vPos , &vAt , &m_vUp );
		}
		m_vDirection.set( m_matrixView[0][2] , m_matrixView[1][2] , m_matrixView[2][2] );
		m_vUp.set( m_matrixView[0][1] , m_matrixView[1][1] , m_matrixView[2][1] );
		m_vRight.set( m_matrixView[0][0] , m_matrixView[1][0] , m_matrixView[2][0] );

		m_matrixInvView = m_matrixView;
		m_matrixInvView.invert();
		m_matrixViewProject = m_matrixView*m_matrixProject;

		CLMatrix3x3 matrixRot = m_matrixView;
		//		m_quatRot.fromRotationMatrix(matrixRot);

		CLPoint position = m_vPos;

		// The axis basis vectors and camera position are stored inside the 
		// position matrix in the 4 rows of the camera's world matrix.
		// To figure out the yaw/pitch of the camera, we just need the Z basis vector
		CLPoint* pZBasis = (CLPoint*) &m_matrixInvView.m[2];

		m_fYawAngle   = atan2f( pZBasis->x, pZBasis->z );
		float fLen = sqrtf(pZBasis->z*pZBasis->z + pZBasis->x*pZBasis->x);
		m_fPitchAngle = -atan2f( pZBasis->y, fLen );

		// -------------------------
		// Update the frustum planes
		// -------------------------
		_updateFrustum();
		// Use Frustum view direction for frustum, which is -Z not Z as for matrix calc
		CLPoint camDirection = m_matrixView * CLPoint(0.f, 0.f, -1.f);
		// Calc distance along direction to position
		float fDdE = -camDirection.dot(position);

		// left plane
		m_frustumPlanes[FrustumPlane_Left].n = -m_fCoeffL[0]*m_vRight +
			m_fCoeffL[1]*camDirection;
		m_frustumPlanes[FrustumPlane_Left].d =
			-position.dot(m_frustumPlanes[FrustumPlane_Left].n);

		// right plane
		m_frustumPlanes[FrustumPlane_Right].n = -m_fCoeffR[0]*m_vRight +
			m_fCoeffR[1]*camDirection;
		m_frustumPlanes[FrustumPlane_Right].d =
			-position.dot(m_frustumPlanes[FrustumPlane_Right].n);

		// bottom plane
		m_frustumPlanes[FrustumPlane_Bottom].n = -m_fCoeffB[0]*m_vUp +
			m_fCoeffB[1]*camDirection;
		m_frustumPlanes[FrustumPlane_Bottom].d =
			-position.dot(m_frustumPlanes[FrustumPlane_Bottom].n);

		// top plane
		m_frustumPlanes[FrustumPlane_Top].n = -m_fCoeffT[0]*m_vUp +
			m_fCoeffT[1]*camDirection;
		m_frustumPlanes[FrustumPlane_Top].d =
			-position.dot(m_frustumPlanes[FrustumPlane_Top].n);

		// far plane
		m_frustumPlanes[FrustumPlane_Far].n = -camDirection;
		//d is distance along normal to origin
		m_frustumPlanes[FrustumPlane_Far].d = -( fDdE + m_fFarDist );
		//// near plane
		m_frustumPlanes[FrustumPlane_Near].n = camDirection;
		m_frustumPlanes[FrustumPlane_Near].d = (fDdE + m_fNearDist);

		// Deal with reflections
		if ( m_bReflectEnabled )
		{
			m_matrixView = m_matrixView * m_matrixReflect;
		}


		m_frustumPlanes[FrustumPlane_Near].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Top], m_frustumPlanes[FrustumPlane_Left], m_pointWorldSpaceCorners[1]);

		m_frustumPlanes[FrustumPlane_Near].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Bottom], m_frustumPlanes[FrustumPlane_Left], m_pointWorldSpaceCorners[2]);

		m_frustumPlanes[FrustumPlane_Near].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Top], m_frustumPlanes[FrustumPlane_Right], m_pointWorldSpaceCorners[0]);

		m_frustumPlanes[FrustumPlane_Near].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Bottom], m_frustumPlanes[FrustumPlane_Right], m_pointWorldSpaceCorners[3]);

		m_frustumPlanes[FrustumPlane_Far].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Top], m_frustumPlanes[FrustumPlane_Left], m_pointWorldSpaceCorners[7]);

		m_frustumPlanes[FrustumPlane_Far].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Bottom], m_frustumPlanes[FrustumPlane_Left], m_pointWorldSpaceCorners[4]);

		m_frustumPlanes[FrustumPlane_Far].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Top], m_frustumPlanes[FrustumPlane_Right], m_pointWorldSpaceCorners[6]);

		m_frustumPlanes[FrustumPlane_Far].getIntersectionWithPlanes(
			m_frustumPlanes[FrustumPlane_Bottom], m_frustumPlanes[FrustumPlane_Right], m_pointWorldSpaceCorners[5]);

		// Deal with reflection on frustum planes
		if ( m_bReflectEnabled )
		{
			CLPoint pos = m_matrixReflect * position;
			CLPoint dir = camDirection.reflect(m_planReflect.n);
			fDdE = dir.dot(pos);
			unsigned int i;
			for (i = 0; i < 6; ++i)
			{
				m_frustumPlanes[i].n = m_frustumPlanes[i].n.reflect(m_planReflect.n);
				// Near / far plane dealt with differently since they don't pass through camera
				switch (i)
				{
				case FrustumPlane_Near:
					m_frustumPlanes[i].d = -(fDdE + m_fNearDist);
					break;
				case FrustumPlane_Far:
					m_frustumPlanes[i].d = fDdE + m_fFarDist;
					break;
				default:
					m_frustumPlanes[i].d = -pos.dot(m_frustumPlanes[i].n);
				}
			}
			// Also reflect corners
			for (i = 0; i < 8; ++i)
			{
				m_pointWorldSpaceCorners[i] = m_matrixReflect * m_pointWorldSpaceCorners[i];
			}
		}


		//		zeusRS->setViewMatrix( m_matrixView );
	}

	m_bViewChanged = false;
}

void CLCamera::_viewChanged()
{
	CLFrustum::_viewChanged();
	//_updateView();
}

void CLCamera::_frustumChanged()
{
	CLFrustum::_frustumChanged();
}


//-------------------------------------------------------------------------
void CLCamera::roll( float fRadian )
{
	// Rotate around local Z axis
	//CLPoint zAxis = m_quatRot * CLPoint( 0.f, 0.f, 1.f );
	rotate(m_vDirection, fRadian);

	//m_fRollAngle += DEGTORAD*fDegrees;
	m_fRollAngle += fRadian;


	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::yaw( float fRadian )
{
	//	rotate(m_vUp, fDegrees);

	//float  fRad = DEGTORAD*fDegrees;
	//m_fYawAngle += fRad;
	m_fYawAngle += fRadian;


	CLMatrix4x4 matrixRot;
	matrixRot.rot(fRadian, CLPoint( 0.f, 1.f, 0.f ) );
	//matrixRot.rot(fRadian, m_vUp );
	CLPoint vPos = m_vPos;

	vPos -= m_vLookAt;
	vPos = vPos * matrixRot;
	vPos += m_vLookAt;

	m_vPos = vPos;

	m_vDirection = m_vLookAt - vPos;
	m_vDirection.normalize();

	m_vRight = CLPoint(0.f, 1.f, 0.f) ^ m_vDirection;
	m_vUp = m_vDirection ^ m_vRight;

	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::pitch( float fRadian )
{
	// Rotate around local x axis
	//CLPoint zAxis = m_quatRot * CLPoint( 1.f, 0.f, 0.f );
	//	rotate(m_vRight, fDegrees);

	//float fRad = DEGTORAD*fDegrees;

	//m_fPitchAngle += fRad;
	m_fPitchAngle += fRadian;


	CLMatrix4x4 matrixRot;
	//matrixRot.rot(fRad, m_vRight);
	matrixRot.rot(fRadian, m_vRight);

	CLPoint vPos = m_vPos;

	vPos -= m_vLookAt;
	vPos = vPos * matrixRot;
	vPos += m_vLookAt;

	m_vPos = vPos;

	m_vDirection = m_vLookAt - vPos;
	m_vDirection.normalize();

	m_vUp = m_vDirection ^ m_vRight;

	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::lookAt( const CLPoint& pointLookAt )
{
	_updateView();

	m_vLookAt = pointLookAt;

	setDirection( pointLookAt-m_vPos, getUp() );
}

//-------------------------------------------------------------------------
void CLCamera::lookAt( float x, float y, float z )
{
	lookAt( CLPoint(x, y, z) );
}

//-------------------------------------------------------------------------
void CLCamera::rotate( const CLPoint& pointAxis, float fRadian )
{
	return ;
	//CLMatrix4x4 matrixRot;
	//matrixRot.rot( DEGTORAD*fDegrees, pointAxis );

	//rotate( matrixRot );

	//float fRad = DEGTORAD*fDegrees;

	//zQuat q;
	//q.fromAngleAxis( fRad , pointAxis );
	//rotate(q);

	//m_vPos = m_vPos * m_matrixRot;

	//float fRad = DEGTORAD*fDegrees;


	CLMatrix4x4 matrixRot;
	//matrixRot.rot(fRad, pointAxis);
	matrixRot.rot(fRadian, pointAxis);

	CLPoint vPos = m_vPos;

	vPos -= m_vLookAt;
	vPos = vPos * matrixRot;
	vPos += m_vLookAt;

	m_vPos = vPos;

	m_vDirection = m_vLookAt - vPos;
	m_vDirection.normalize();

	m_vRight = CLPoint(0.f, 1.f, 0.f) ^ m_vDirection;
	m_vUp = m_vDirection ^ m_vRight;
}


//-------------------------------------------------------------------------
//void CLCamera::setDirection( float x, float y, float z )
//{
//	setDirection( CLPoint(x, y, z), getUp() );
//}

//-------------------------------------------------------------------------
void CLCamera::setDirection( const CLPoint& pointDirection, const CLPoint& pointUp )
{
	// Do nothing if given a zero vector
	// (Replaced assert since this could happen with auto tracking camera and
	//  camera passes through the lookAt point)
	if ( !pointDirection.isValid() ) return;

	m_vDirection = pointDirection;
	m_vDirection.normalize();

	m_vRight = pointUp ^ pointDirection;
	m_vRight.normalize();
	m_vUp = pointUp;
	//	m_vUp.normalize();

	//	m_vLookAt = m_vPos + m_vDirection;

	_viewChanged();
}

//-------------------------------------------------------------------------
const CLPoint &CLCamera::getDirection() const
{
	// Direction points down -Z by default
	return m_vDirection;
}

//-------------------------------------------------------------------------
const CLPoint &CLCamera::getRight() const
{
	return m_vRight;
}

//-------------------------------------------------------------------------
const CLPoint &CLCamera::getUp() const
{
	return m_vUp;
}

//-------------------------------------------------------------------------
void CLCamera::setPosition(const CLPoint &pointPos)
{
	m_vPos = pointPos;

	_viewChanged();
}

//-------------------------------------------------------------------------
const CLPoint &CLCamera::getPosition()
{
	_updateView();

	return m_vPos;
}

//-------------------------------------------------------------------------
void CLCamera::move( const CLPoint &point )
{
	m_vPos = m_vPos + point;
	//	m_vLookAt = m_vLookAt + point;
	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::moveRelative( const CLPoint &point )
{
	// Transform the axes of the relative vector by camera's local axes
	CLPoint trans = m_matrixView * point;

	m_vPos = m_vPos + trans;
	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::moveForward( float fDistance )
{
	CLPoint vDirection = getDirection();

	float fProLen=sqrtf( 1.f-vDirection.y*vDirection.y );
	if(fProLen!=0.f)
	{
		m_vPos.x += (vDirection.x/fProLen)*fDistance;
		m_vPos.z += (vDirection.z/fProLen)*fDistance;

		m_vLookAt.x += (vDirection.x/fProLen)*fDistance;
		m_vLookAt.z += (vDirection.z/fProLen)*fDistance;
	}
	else
	{
		m_vPos.z += fDistance;
	}

	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::moveRight( float fDistance  )
{
	CLPoint vDirection = getDirection();

	float fProLen=sqrtf( 1.f-vDirection.y*vDirection.y );
	if(fProLen!=0.f)
	{
		m_vPos.x += (vDirection.z/fProLen)*fDistance;
		m_vPos.z -= (vDirection.x/fProLen)*fDistance;

		m_vLookAt.x += (vDirection.z/fProLen)*fDistance;
		m_vLookAt.z -= (vDirection.x/fProLen)*fDistance;
	}
	else
	{
		m_vPos.z += fDistance;
	}

	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::moveUp( float fDistance  )
{
	//	m_vPos += getUp()*fDistance;

	//	m_vLookAt += getUp()*fDistance;

	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::zoom( float fDist )
{
	m_vPos += m_vDirection*fDist;
	//	m_vLookAt = m_vPos + m_vDirection;
	_viewChanged();
}

//-------------------------------------------------------------------------
void CLCamera::onScreenChanged( int nScreenWidth, int nScreenHeight )
{

	setAspect( nScreenWidth/(float)nScreenHeight );

	//int left, top, width, height;
	//if(zeusRS)
	//{
	//   zeusRS->getViewport( left, top, width, height );
	//   zeusRS->setViewport( left, top, nScreenWidth, nScreenHeight );
	//}
}

//-------------------------------------------------------------------------
CLRay CLCamera::getCameraRay( float screenX, float screenY )
{
	CLPoint screenPos( screenX, screenY, 1.f );

	CLRay rayCamera;
	rayCamera.mOrig = m_vPos;


	int left, top, width, height;
	GetLaborCore()->getRenderSystem()->getViewport( left, top, width, height );

	// Compute the vector of the pick ray in screen space
	CLPoint v;
	v.x =  ( ( ( 2.0f * screenX ) / width  ) - 1 ) / m_matrixProject.m[0][0];
	v.y = -( ( ( 2.0f * screenY ) / height ) - 1 ) / m_matrixProject.m[1][1];
	v.z =  1.0f;

	// Get the inverse view matrix
	CLMatrix4x4&  m = m_matrixInvView;

	// Transform the screen space pick ray into 3D space
	rayCamera.mDir.x  = v.x*m.m[0][0] + v.y*m.m[1][0] + v.z*m.m[2][0];
	rayCamera.mDir.y  = v.x*m.m[0][1] + v.y*m.m[1][1] + v.z*m.m[2][1];
	rayCamera.mDir.z  = v.x*m.m[0][2] + v.y*m.m[1][2] + v.z*m.m[2][2];
	rayCamera.mOrig.x = m.m[3][0];
	rayCamera.mOrig.y = m.m[3][1];
	rayCamera.mOrig.z = m.m[3][2];

	return rayCamera;
}


NAMESPACE_LABOR3D_END